import { InfiniteScrollViewCpnt } from "../../../../JKCore/Script/Components/InfiniteScrollViewCpnt";
import { JKManager } from "../../../../JKCore/Script/JKManager";
import { LocalizedLabel } from "../../../../JKCore/Script/Language/LocalizedLabel";
import { NormalViewBase } from "../../../../JKCore/Script/View/NormalViewBase";

const { ccclass, property } = cc._decorator;

type DataIsv = {
    row: number,
    longCol: number,
    shortCol: number,
}

type BubbleData = {
    id: string,
    x: number,
    y: number,
}

type GridDatas = GridData[][]

type GridData = number | number[]

let NormalBubbleType1 = `1`,
    NormalBubbleType2 = `2`,
    SpecialBubbleType = `3`,
    DoubleBubbleType = `4`,
    AttachmentType = `5`,
    Attachment2Type = `6`,
    RandomBubbleType = `7`

let RowItemSpace = -5,
    IdleOpacity = 50,
    DefaultBubbleId = `0`

/**
 * 登录界面
 */
@ccclass
export class MissionEditorView extends NormalViewBase {
    @property(cc.Prefab)
    pfbIsvCpnt: cc.Prefab = null;

    ndSettingCtn: cc.Node;
    ndIsvCpntCtn: cc.Node;
    ndRowItem: cc.Node;
    ndBubbleItem: cc.Node;
    ebRow: cc.EditBox;
    ebLongCol: cc.EditBox;
    ebshortCol: cc.EditBox;
    labRowCnt: LocalizedLabel;
    labBubblesCnt: LocalizedLabel;
    labNormal1BubblesCnt: LocalizedLabel;
    labNormal2BubblesCnt: LocalizedLabel;
    labDoubleBubblesCnt: LocalizedLabel;
    labSpecialBubblesCnt: LocalizedLabel;
    labAttachmentsCnt: LocalizedLabel;
    labAttachments2Cnt: LocalizedLabel;
    labRandomBubblesCnt: LocalizedLabel;
    ndBtnRandomDetails: cc.Node;
    ndBubbleList: cc.Node;
    ndBubbleListCtn: cc.Node;
    ndNormalBubble: cc.Node;
    ndNormalBubbleCtn: cc.Node;
    ndSpecialBubble: cc.Node;
    ndSpecialBubbleCtn: cc.Node;
    ndDoubleBubble: cc.Node;
    ndDoubleBubbleCtn: cc.Node;
    ndAttachment: cc.Node;
    ndAttachmentCtn: cc.Node;
    ndAttachment2: cc.Node;
    ndAttachment2Ctn: cc.Node;
    ndRandomBubble: cc.Node;
    ndRandomBubbleCtn: cc.Node;
    ndBubbleListItem: cc.Node;
    scIsvCpnt: InfiniteScrollViewCpnt;
    ndSaving: cc.Node;
    ndBtnImport: cc.Node;
    ndBtnExport: cc.Node;
    ebImportPath: cc.EditBox;
    ebExportPath: cc.EditBox;
    private _gridData: GridDatas;
    private _curBubbleId: string;
    private _mouseDownTarget: cc.Node;
    private _curBubble: cc.Node;
    private _bubbleSpfsMap: { [id: string]: cc.SpriteFrame };
    private _mouseDownButton: number;
    private _mouseDownButtonLeft: boolean;
    private _mouseDownButtonRight: boolean;
    private _bubblesRecord: BubbleData[];
    private _rightMouseMovedBubbleItems: { [gridPosKey: string]: cc.Node };

    onLoad(): void {
        super.onLoad()
        cc.log(`${cc.js.getClassName(this)} onLoad`)

        // Ui
        this.ndSettingCtn = cc.find(`ndSetting/ndCtn`, this.node)
        this.ebRow = cc.find(`ndEbCtn/ebRow`, this.ndSettingCtn).getComponent(cc.EditBox)
        this.ebLongCol = cc.find(`ndEbCtn/ebLongCol`, this.ndSettingCtn).getComponent(cc.EditBox)
        this.ebshortCol = cc.find(`ndEbCtn/ebshortCol`, this.ndSettingCtn).getComponent(cc.EditBox)
        this.labRowCnt = cc.find(`ndCtn1/ndCtn1/labRowCnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.labBubblesCnt = cc.find(`ndCtn1/ndCtn2/labBubblesCnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.labNormal1BubblesCnt = cc.find(`ndCtn2/ndCtn1/labNormal1BubblesCnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.labNormal2BubblesCnt = cc.find(`ndCtn2/ndCtn2/labNormal2BubblesCnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.labDoubleBubblesCnt = cc.find(`ndCtn3/ndCtn1/labDoubleBubblesCnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.labSpecialBubblesCnt = cc.find(`ndCtn3/ndCtn2/labSpecialBubblesCnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.labAttachmentsCnt = cc.find(`ndCtn4/ndCtn1/labAttachmentsCnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.labAttachments2Cnt = cc.find(`ndCtn4/ndCtn2/labAttachments2Cnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.labRandomBubblesCnt = cc.find(`ndCtn5/ndCtn1/labRandomBubblesCnt`, this.ndSettingCtn).getComponent(LocalizedLabel)
        this.ndBtnRandomDetails = cc.find(`ndCtn5/ndCtn2/ndBtnRandomDetails`, this.ndSettingCtn)
        this.ndSaving = cc.find(`ndSaving`, this.node)
        this.ndBtnImport = cc.find(`ndBtnImport`, this.ndSaving)
        this.ndBtnExport = cc.find(`ndBtnExport`, this.ndSaving)
        this.ebImportPath = cc.find(`ebImportPath`, this.ndSaving).getComponent(cc.EditBox)
        this.ebExportPath = cc.find(`ebExportPath`, this.ndSaving).getComponent(cc.EditBox)

        // Isv
        this.ndIsvCpntCtn = cc.find(`ndIsvCpntCtn`, this.node)
        this.ndRowItem = cc.find(`ndRowItem`, this.ndIsvCpntCtn)
        this.ndBubbleItem = cc.find(`ndBubbleItem`, this.ndRowItem)
        this.ndBubbleItem.parent = null
        this.ndRowItem.parent = null
        this.scIsvCpnt = cc.instantiate(this.pfbIsvCpnt).getComponent(this.pfbIsvCpnt.data.name)
        this.ndIsvCpntCtn.addChild(this.scIsvCpnt.node)

        // BubbleList
        this.ndBubbleList = cc.find(`ndBubbleList`, this.node)
        this.ndBubbleListCtn = cc.find(`scrollView/view/content`, this.ndBubbleList)
        this.ndNormalBubble = cc.find(`ndNormalBubble`, this.ndBubbleListCtn)
        this.ndNormalBubbleCtn = cc.find(`ndCtn`, this.ndNormalBubble)
        this.ndSpecialBubble = cc.find(`ndSpecialBubble`, this.ndBubbleListCtn)
        this.ndSpecialBubbleCtn = cc.find(`ndCtn`, this.ndSpecialBubble)
        this.ndDoubleBubble = cc.find(`ndDoubleBubble`, this.ndBubbleListCtn)
        this.ndDoubleBubbleCtn = cc.find(`ndCtn`, this.ndDoubleBubble)
        this.ndAttachment = cc.find(`ndAttachment`, this.ndBubbleListCtn)
        this.ndAttachmentCtn = cc.find(`ndCtn`, this.ndAttachment)
        this.ndAttachment2 = cc.find(`ndAttachment2`, this.ndBubbleListCtn)
        this.ndAttachment2Ctn = cc.find(`ndCtn`, this.ndAttachment2)
        this.ndRandomBubble = cc.find(`ndRandomBubble`, this.ndBubbleListCtn)
        this.ndRandomBubbleCtn = cc.find(`ndCtn`, this.ndRandomBubble)
        this.ndBubbleListItem = cc.find(`ndBubbleListItem`, this.ndNormalBubbleCtn)
        this.ndBubbleListItem.parent = null
    }

    init(data: any): void {
        cc.log(`${cc.js.getClassName(this)} init data = `, data)

        // data = {
        // }

        // Ui
        let dataUi = {
        }
        this._dataUpdateTool.put(JKManager.Const.MissionEditorView.CpntName.Ui, dataUi)
        this.ebRow.node.on(`editing-did-ended`, (editbox: cc.EditBox) => {
            cc.log(`${cc.js.getClassName(this)} ebRow editing-did-ended`)

            this._dataUpdateTool.setCpntDataByName(JKManager.Const.MissionEditorView.CpntName.Isv, {
                row: Number(this.ebRow.string),
                longCol: Number(this.ebLongCol.string),
                shortCol: Number(this.ebshortCol.string),
            })
        })
        this.ebLongCol.node.on(`editing-did-ended`, (editbox: cc.EditBox) => {
            cc.log(`${cc.js.getClassName(this)} ebLongCol editing-did-ended`)

            this._dataUpdateTool.setCpntDataByName(JKManager.Const.MissionEditorView.CpntName.Isv, {
                row: Number(this.ebRow.string),
                longCol: Number(this.ebLongCol.string),
                shortCol: Number(this.ebshortCol.string),
            })
        })
        this.ebshortCol.node.on(`editing-did-ended`, (editbox: cc.EditBox) => {
            cc.log(`${cc.js.getClassName(this)} ebshortCol editing-did-ended`)

            this._dataUpdateTool.setCpntDataByName(JKManager.Const.MissionEditorView.CpntName.Isv, {
                row: Number(this.ebRow.string),
                longCol: Number(this.ebLongCol.string),
                shortCol: Number(this.ebshortCol.string),
            })
        })
        this.ndBtnImport.on(cc.Node.EventType.TOUCH_END, (event: cc.Event.EventTouch) => {
            cc.log(`${cc.js.getClassName(this)} onBtnImport`)

            this._importGridDate()
        }, this)
        this.ndBtnExport.on(cc.Node.EventType.TOUCH_END, (event: cc.Event.EventTouch) => {
            cc.log(`${cc.js.getClassName(this)} onBtnExport`)

            this._exportGridDate()
        }, this)
        this.ndBtnRandomDetails.on(cc.Node.EventType.TOUCH_END, (event: cc.Event.EventTouch) => {
            cc.log(`${cc.js.getClassName(this)} onBtnRandomDetails`)

            let msg = this._getRandomCntDetails()
            JKManager.UiManager.showCommonDialogView(msg, `ScaleIn`, () => {

            })
        }, this)
        this._bubblesRecord = []

        // Isv
        let dataIsv: DataIsv = {
            row: Number(this.ebRow.string),
            longCol: Number(this.ebLongCol.string),
            shortCol: Number(this.ebshortCol.string),
        }
        this._dataUpdateTool.put(JKManager.Const.MissionEditorView.CpntName.Isv, dataIsv)
        this.scIsvCpnt.setItemSpace(RowItemSpace)

        // BubbleList
        let dataBubbleList: [string[], string[], string[],] = [[], [], [],]
        this._dataUpdateTool.put(JKManager.Const.MissionEditorView.CpntName.BubbleList, dataBubbleList)
        this._curBubbleId = `401`
        this._bubbleSpfsMap = cc.js.createMap()
        this.ndBubbleItem.getComponent(cc.Sprite).spriteFrame.name = DefaultBubbleId
        this._bubbleSpfsMap[DefaultBubbleId] = new cc.SpriteFrame(this.ndBubbleItem.getComponent(cc.Sprite).spriteFrame.getTexture())
        this._bubbleSpfsMap[DefaultBubbleId].name = DefaultBubbleId
        this._rightMouseMovedBubbleItems = cc.js.createMap()
    }

    start(): void {
        cc.log(`${cc.js.getClassName(this)} start`)

        // Ui
        this._dataUpdateTool.registCpntUpdateWithName(JKManager.Const.MissionEditorView.CpntName.Ui, this._updateCpntUi.bind(this))
        this._updateCpntUi(this._dataUpdateTool.getCpntDataByName(JKManager.Const.MissionEditorView.CpntName.Ui), true)

        // Isv
        this._dataUpdateTool.registCpntUpdateWithName(JKManager.Const.MissionEditorView.CpntName.Isv, this._updateCpntIsv.bind(this))
        this._updateCpntIsv(this._dataUpdateTool.getCpntDataByName(JKManager.Const.MissionEditorView.CpntName.Isv), true)

        // BubbleList
        this._dataUpdateTool.registCpntUpdateWithName(JKManager.Const.MissionEditorView.CpntName.BubbleList, this._updateCpntBubbleList.bind(this))
        this._updateCpntBubbleList(this._dataUpdateTool.getCpntDataByName(JKManager.Const.MissionEditorView.CpntName.BubbleList), true)
    }

    update(dt: number): void {
        super.update(dt)
    }

    close(): void {
        cc.log(`${cc.js.getClassName(this)} close`)

        let data = {}
        JKManager.UiManager.backNormalView(data)
    }

    onDestroy(): void {
        cc.log(`${cc.js.getClassName(this)} onDestroy`)
    }

    // ====================================================================================
    // ===================================== 内部接口 ======================================
    // ====================================================================================

    /**
     * 刷新组件 Ui
     * @param data 数据对象
     * @param immediately 是否同步刷新
     */
    private _updateCpntUi(data: any, immediately: boolean = false): void {
        cc.log(`${cc.js.getClassName(this)} _updateCpntUi data = `, data)

        this._taskQueueTool.addCmd({
            cmd: JKManager.Const.MissionEditorView.Cmd.UpdateCpntUi,
            params: {},
            call: (cmd, params) => {
                cc.log(`${cc.js.getClassName(this)} Cmd.UpdateCpntUi params = `, params)

                this._taskQueueTool.doneCmd(cmd)
            }
        }, immediately)
    }

    /**
     * 刷新组件 Isv
     * @param data 数据对象
     * @param immediately 是否同步刷新
     */
    private _updateCpntIsv(data: DataIsv, immediately: boolean = false): void {
        cc.log(`${cc.js.getClassName(this)} _updateCpntIsv data = `, data)

        this._taskQueueTool.addCmd({
            cmd: JKManager.Const.MissionEditorView.Cmd.UpdateCpntIsv,
            params: {},
            call: (cmd, params) => {
                cc.log(`${cc.js.getClassName(this)} Cmd.UpdateCpntIsv params = `, params)

                this._gridData = this._gridData || cc.js.createMap()
                for (let y = 0; y < data.row; y++) {
                    let yData = this._gridData[y] || cc.js.createMap(),
                        yLen: number
                    if (y % 2 === 0) {
                        yLen = 11
                    } else {
                        yLen = 10
                    }
                    for (let x = 0; x < yLen; x++) {
                        yData[x] = yData[x] || DefaultBubbleId
                    }
                    for (let z = yLen; z < 11; z++) {
                        delete yData[z]
                    }
                    this._gridData[y] = yData
                }
                let xLen = Object.keys(this._gridData).length
                for (let y = data.row; y < xLen; y++) {
                    delete this._gridData[y]
                }
                xLen = Object.keys(this._gridData).length
                this.scIsvCpnt.init(xLen, (index: number, isIniting) => {
                    // cc.log(`${cc.js.getClassName(this)} onIsvCreateCall index = ${index}`)

                    let item: cc.Node = this.scIsvCpnt.getItemFromPoolWithName(this.ndRowItem.name) || cc.instantiate(this.ndRowItem),
                        yData = this._gridData[index],
                        yLen = Object.keys(yData).length
                    for (let x = 0; x < yLen; x++) {
                        let ndBubbleItem = item.children[x] || cc.instantiate(this.ndBubbleItem),
                            widget = ndBubbleItem.getComponent(cc.Widget)
                        ndBubbleItem.width = ndBubbleItem.height
                        !ndBubbleItem.parent && item.addChild(ndBubbleItem)
                        if (index === 0) {
                            item.height = this.ndRowItem.height - RowItemSpace
                            widget.top = 0
                            widget.bottom = 0
                            widget.isAlignTop = false
                            widget.isAlignBottom = true
                        } else if (index === xLen - 1) {
                            item.height = this.ndRowItem.height + 5
                            widget.top = 0
                            widget.bottom = 0
                            widget.isAlignTop = true
                            widget.isAlignBottom = false
                        } else {
                            item.height = this.ndRowItem.height
                            widget.top = 0
                            widget.bottom = 0
                            widget.isAlignTop = true
                            widget.isAlignBottom = true
                        }
                    }
                    for (let x = yLen; x < item.children.length; x++) {
                        let ndBubbleItem = item.children[x]
                        if (ndBubbleItem) {
                            ndBubbleItem.width = 0
                        }
                    }

                    return {
                        item: item,
                        poolName: item.name,
                    }
                }, (item: cc.Node, index: number, isActive, isIniting) => {
                    // cc.log(`${cc.js.getClassName(this)} onIsvActiveCall index = ${index} isActive = ${isActive}`)

                    if (isActive) {
                        let yData = this._gridData[index],
                            yLen = Object.keys(yData).length
                        for (let x = 0; x < yLen; x++) {
                            let id = this._gridDataToIdString(yData[x]),
                                ndBubbleItem = item.children[x]
                            if (this._bubbleSpfsMap) {
                                if (this._hasAttachmentId(id) || this._hasAttachment2Id(id)) {
                                    let idArr = JKManager.Utils.parseJSON(id) as number[] | number,
                                        bubbleId = idArr[0].toString(),
                                        attachmentId = idArr[1].toString(),
                                        attachment2Id = idArr[2] && idArr[2].toString()
                                    bubbleId && bubbleId != DefaultBubbleId && this._refreshBubble(ndBubbleItem, bubbleId, index, x)
                                    attachmentId && attachmentId != DefaultBubbleId && this._refreshBubble(ndBubbleItem, attachmentId, index, x)
                                    attachment2Id && attachment2Id != DefaultBubbleId && this._refreshBubble(ndBubbleItem, attachment2Id, index, x)
                                } else {
                                    this._refreshBubble(ndBubbleItem, id.toString(), index, x)
                                }
                            }
                            ndBubbleItem.on(cc.Node.EventType.MOUSE_DOWN, (event: cc.Event.EventMouse) => {
                                // cc.log(`${cc.js.getClassName(this)} onMouseDown`)

                                this._mouseDownTarget = event.target
                                this._mouseDownButton = event.getButton()
                                this._mouseDownButtonLeft = this._mouseDownButtonLeft || event.getButton() === cc.Event.EventMouse.BUTTON_LEFT
                                this._mouseDownButtonRight = this._mouseDownButtonRight || event.getButton() === cc.Event.EventMouse.BUTTON_RIGHT
                                // cc.log(`${cc.js.getClassName(this)} this._mouseDownButtonLeft = ${this._mouseDownButtonLeft} this._mouseDownButtonRight = ${this._mouseDownButtonRight}`)
                                this.scIsvCpnt.lockScroll(!this._mouseDownButtonLeft || !this._mouseDownButtonRight)
                            }, this)
                            ndBubbleItem.on(cc.Node.EventType.MOUSE_MOVE, (event: cc.Event.EventMouse) => {
                                if (this._mouseDownTarget) {
                                    if (this._mouseDownButton === cc.Event.EventMouse.BUTTON_LEFT) {
                                        // cc.log(`${cc.js.getClassName(this)} onMouseLeft`)

                                        this._refreshBubble(ndBubbleItem, this._curBubbleId, index, x)
                                    } else if (this._mouseDownButton === cc.Event.EventMouse.BUTTON_RIGHT) {
                                        // cc.log(`${cc.js.getClassName(this)} onMouseRight`)

                                        if (!this._rightMouseMovedBubbleItems[`[${index},${x}]`]) {
                                            this._refreshBubble(ndBubbleItem, DefaultBubbleId, index, x)
                                            this._rightMouseMovedBubbleItems[`[${index},${x}]`] = ndBubbleItem
                                        }
                                    }
                                }
                            }, this)
                            ndBubbleItem.on(cc.Node.EventType.MOUSE_UP, (event: cc.Event.EventMouse) => {
                                if (this._mouseDownTarget === event.target) {
                                    if (event.getButton() === cc.Event.EventMouse.BUTTON_LEFT) {
                                        // cc.log(`${cc.js.getClassName(this)} onMouseLeft`)

                                        this._refreshBubble(ndBubbleItem, this._curBubbleId, index, x)
                                    } else if (event.getButton() === cc.Event.EventMouse.BUTTON_RIGHT) {
                                        // cc.log(`${cc.js.getClassName(this)} onMouseRight`)

                                        this._refreshBubble(ndBubbleItem, DefaultBubbleId, index, x)
                                    }
                                }
                                if (event.getButton() === cc.Event.EventMouse.BUTTON_RIGHT) {
                                    this._rightMouseMovedBubbleItems = cc.js.createMap()
                                }
                                this._mouseDownTarget = null
                                this._mouseDownButton = null
                                this._mouseDownButtonLeft = false
                                this._mouseDownButtonRight = false
                                this.scIsvCpnt.lockScroll(false)
                            }, this)
                        }
                    } else {
                        item.children.forEach((ndBubbleItem, x) => {
                            if (isIniting && index < Object.keys(this._gridData).length) {
                                this._refreshBubble(ndBubbleItem, DefaultBubbleId)
                            } else {
                                this._refreshBubble(ndBubbleItem, DefaultBubbleId, index, x)
                            }
                            ndBubbleItem.targetOff(this)
                        })
                    }
                }, false, true)

                this._taskQueueTool.doneCmd(cmd)
            }
        }, immediately)
    }

    /**
     * 刷新组件 BubbleList
     * @param data 数据对象
     * @param immediately 是否同步刷新
     */
    private _updateCpntBubbleList(data: [string[], string[], string[],], immediately: boolean = false): void {
        cc.log(`${cc.js.getClassName(this)} _updateCpntBubbleList data = `, data)

        this._taskQueueTool.addCmd({
            cmd: JKManager.Const.MissionEditorView.Cmd.UpdateCpntBubbleList,
            params: {},
            call: (cmd, params) => {
                cc.log(`${cc.js.getClassName(this)} Cmd.UpdateCpntBubbleList params = `, params)

                let loadBubbles = (dir: string, ndCtn: cc.Node) => {
                    cc.resources.loadDir(`BubbleShooter/Texture/Bubble/${dir}`, cc.SpriteFrame, (error: Error, spfs: cc.SpriteFrame[]) => {
                        if (error) {
                            cc.error(error)
                        } else {
                            spfs.forEach((spf: cc.SpriteFrame, index: number) => {
                                let id = spf.name,
                                    ndBubbleItem = ndCtn.children[index] || cc.instantiate(this.ndBubbleListItem),
                                    spNormalBubbleItem = ndBubbleItem.getComponent(cc.Sprite),
                                    ndBubble = cc.find(`ndBubble`, ndBubbleItem)
                                this._bubbleSpfsMap[id] = spf
                                !ndBubbleItem.parent && ndCtn.addChild(ndBubbleItem)
                                if (id === this._curBubbleId) {
                                    spNormalBubbleItem.enabled = true
                                    this._curBubble = ndBubbleItem
                                } else {
                                    spNormalBubbleItem.enabled = false
                                }
                                this._refreshBubble(ndBubble, id)
                                if (this._bubbleSpfsMap) {
                                    let spBubble = ndBubble.getComponent(cc.Sprite)
                                    spBubble.spriteFrame = this._bubbleSpfsMap[id]
                                }
                                ndBubbleItem.targetOff(this)
                                ndBubbleItem.on(cc.Node.EventType.TOUCH_END, (event: cc.Event.EventTouch) => {
                                    cc.log(`${cc.js.getClassName(this)} on${dir}Item`)

                                    this._curBubble.getComponent(cc.Sprite).enabled = false
                                    this._curBubble = ndBubbleItem
                                    this._curBubble.getComponent(cc.Sprite).enabled = true
                                    this._curBubbleId = id
                                }, this)
                            })
                        }
                    })
                }
                loadBubbles(`DoubleBubble`, this.ndDoubleBubbleCtn)
                loadBubbles(`NormalBubble`, this.ndNormalBubbleCtn)
                loadBubbles(`SpecialBubble`, this.ndSpecialBubbleCtn)
                loadBubbles(`Attachment`, this.ndAttachmentCtn)
                loadBubbles(`Attachment2`, this.ndAttachment2Ctn)
                loadBubbles(`RandomBubble`, this.ndRandomBubbleCtn)

                this._taskQueueTool.doneCmd(cmd)
            }
        }, immediately)
    }

    // ====================================================================================

    /**
     * 刷新泡泡
     * @param ndBubble 泡泡节点
     * @param id 泡泡id
     * @param y 泡泡网格坐标y
     * @param x 泡泡网格坐标x
     */
    private _refreshBubble(ndBubble: cc.Node, id: string, y: number = null, x: number = null) {
        // cc.log(`${cc.js.getClassName(this)} _refreshBubble id = ${id} y = ${y} x = ${x}`)

        if (y !== null && x !== null) {
            // 刷新网格上的泡泡
            let yData = this._gridData[y]
            if (yData) {
                let dataId = this._gridDataToIdString(yData[x])
                if (dataId) {
                    if ((this._isAttachment2Id(id) && dataId !== DefaultBubbleId) || (id === DefaultBubbleId && this._hasAttachment2Id(dataId))) {
                        // 刷新2级附着物
                        if (id !== DefaultBubbleId) {
                            dataId = this._addAttachment2Id(dataId, id)
                        } else {
                            dataId = this._removeAttachment2Id(dataId)
                        }
                    } else if ((this._isAttachmentId(id) && dataId !== DefaultBubbleId) || (id === DefaultBubbleId && this._hasAttachmentId(dataId))) {
                        // 刷新附着物
                        if (id !== DefaultBubbleId) {
                            dataId = this._addAttachmentId(dataId, id)
                        } else {
                            dataId = this._removeAttachmentId(dataId)
                        }
                    } else if (!this._isAttachmentId(id) && !this._isAttachment2Id(id)) {
                        // 刷新泡泡
                        dataId = id
                    }
                    yData[x] = JKManager.Utils.parseJSON(dataId) as GridData
                }
            }
        }

        if (this._bubbleSpfsMap) {
            let spBubble = ndBubble.getComponent(cc.Sprite),
                spAttachment = cc.find(`ndAttachment`, ndBubble).getComponent(cc.Sprite),
                spAttachment2 = cc.find(`ndAttachment2`, ndBubble).getComponent(cc.Sprite),
                bubbleId = spBubble.spriteFrame.name || DefaultBubbleId,
                attachmentId = spAttachment.spriteFrame && spAttachment.spriteFrame.name,
                attachment2Id = spAttachment2.spriteFrame && spAttachment2.spriteFrame.name
            if ((this._isAttachment2Id(id) && bubbleId !== DefaultBubbleId) || (id === DefaultBubbleId && attachment2Id)) {
                // 刷新2级附着物
                if (bubbleId && bubbleId !== id) {
                    let bubbleData: BubbleData = cc.js.createMap()
                    bubbleData.x = x
                    bubbleData.y = y
                    if (id !== DefaultBubbleId) {
                        bubbleData.id = this._addAttachment2Id(bubbleId, id)
                        spAttachment2.spriteFrame = this._bubbleSpfsMap[id]
                    } else {
                        bubbleData.id = this._removeAttachment2Id(bubbleId)
                        spAttachment2.spriteFrame = null
                    }
                    this._addBubblesRecord([bubbleData])
                }
            } else if ((this._isAttachmentId(id) && bubbleId !== DefaultBubbleId) || (id === DefaultBubbleId && attachmentId)) {
                // 刷新附着物
                if (bubbleId && bubbleId !== id) {
                    let bubbleData: BubbleData = cc.js.createMap()
                    bubbleData.x = x
                    bubbleData.y = y
                    if (id !== DefaultBubbleId) {
                        bubbleData.id = this._addAttachmentId(bubbleId, id)
                        spAttachment.spriteFrame = this._bubbleSpfsMap[id]
                    } else {
                        bubbleData.id = this._removeAttachmentId(bubbleId)
                        spAttachment.spriteFrame = null
                    }
                    this._addBubblesRecord([bubbleData])
                }
            } else if (!this._isAttachmentId(id) && !this._isAttachment2Id(id)) {
                // 刷新泡泡
                if (bubbleId !== id) {
                    let bubbleData: BubbleData = cc.js.createMap()
                    bubbleData.id = id
                    bubbleData.x = x
                    bubbleData.y = y
                    if (id !== DefaultBubbleId) {
                        this._addBubblesRecord([bubbleData])
                        spBubble.spriteFrame = this._bubbleSpfsMap[id]
                    } else {
                        this._removeBubblesRecord([bubbleData])
                        spBubble.spriteFrame = this._bubbleSpfsMap[DefaultBubbleId]
                    }
                }
            }

            // 刷新统计显示
            if (this._bubblesRecord.length > 0) {
                this.labRowCnt.textKey = (this._bubblesRecord[this._bubblesRecord.length - 1].y + 1).toString()
                this.labBubblesCnt.textKey = this._bubblesRecord.length.toString()
                let normal1BubblesCnt = 0,
                    normal2BubblesCnt = 0,
                    doubleBubblesCnt = 0,
                    specialBubblesCnt = 0,
                    attachmentsCnt = 0,
                    attachments2Cnt = 0,
                    randomBubblesCnt = 0
                this._bubblesRecord.forEach(bubbleData => {
                    if (this._hasNormal1BubbleId(bubbleData.id)) {
                        normal1BubblesCnt++
                    }
                    if (this._hasNormal2BubbleId(bubbleData.id)) {
                        normal2BubblesCnt++
                    }
                    if (this._hasDoubleBubbleId(bubbleData.id)) {
                        doubleBubblesCnt++
                    }
                    if (this._hasSpecialBubbleId(bubbleData.id)) {
                        specialBubblesCnt++
                    }
                    if (this._hasAttachmentId(bubbleData.id)) {
                        attachmentsCnt++
                    }
                    if (this._hasAttachment2Id(bubbleData.id)) {
                        attachments2Cnt++
                    }
                    if (this._hasRandomBubbleId(bubbleData.id)) {
                        randomBubblesCnt++
                    }
                })
                this.labNormal1BubblesCnt.textKey = normal1BubblesCnt.toString()
                this.labNormal2BubblesCnt.textKey = normal2BubblesCnt.toString()
                this.labDoubleBubblesCnt.textKey = doubleBubblesCnt.toString()
                this.labSpecialBubblesCnt.textKey = specialBubblesCnt.toString()
                this.labAttachmentsCnt.textKey = attachmentsCnt.toString()
                this.labAttachments2Cnt.textKey = attachments2Cnt.toString()
                this.labRandomBubblesCnt.textKey = randomBubblesCnt.toString()
            } else {
                this.labRowCnt.textKey = `0`
                this.labBubblesCnt.textKey = `0`
                this.labNormal1BubblesCnt.textKey = `0`
                this.labNormal2BubblesCnt.textKey = `0`
                this.labDoubleBubblesCnt.textKey = `0`
                this.labSpecialBubblesCnt.textKey = `0`
                this.labAttachmentsCnt.textKey = `0`
                this.labAttachments2Cnt.textKey = `0`
                this.labRandomBubblesCnt.textKey = `0`
            }
        }
    }

    /**
     * 指定泡泡id是否含有附着物id
     * @param id 指定泡泡id
     */
    private _hasNormal1BubbleId(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _hasNormal1BubbleId id = ${id}`)

        let idArr = JKManager.Utils.parseJSON(id) as number[] | number,
            bubbleId: string
        if (id.indexOf(`,`) >= 0) {
            bubbleId = idArr[0].toString()
        } else {
            bubbleId = id
        }

        return bubbleId[0] === NormalBubbleType1
    }

    /**
     * 指定泡泡id是否含有附着物id
     * @param id 指定泡泡id
     */
    private _hasNormal2BubbleId(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _hasNormal2BubbleId id = ${id}`)

        let idArr = JKManager.Utils.parseJSON(id) as number[] | number,
            bubbleId: string
        if (id.indexOf(`,`) >= 0) {
            bubbleId = idArr[0].toString()
        } else {
            bubbleId = id
        }

        return bubbleId[0] === NormalBubbleType2
    }

    /**
     * 指定泡泡id是否含有附着物id
     * @param id 指定泡泡id
     */
    private _hasDoubleBubbleId(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _hasDoubleBubbleId id = ${id}`)

        let idArr = JKManager.Utils.parseJSON(id) as number[] | number,
            bubbleId: string
        if (id.indexOf(`,`) >= 0) {
            bubbleId = idArr[0].toString()
        } else {
            bubbleId = id
        }

        return bubbleId[0] === DoubleBubbleType
    }

    /**
     * 指定泡泡id是否含有附着物id
     * @param id 指定泡泡id
     */
    private _hasSpecialBubbleId(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _hasSpecialBubbleId id = ${id}`)

        let idArr = JKManager.Utils.parseJSON(id) as number[] | number,
            bubbleId: string
        if (id.indexOf(`,`) >= 0) {
            bubbleId = idArr[0].toString()
        } else {
            bubbleId = id
        }

        return bubbleId[0] === SpecialBubbleType
    }

    /**
     * 指定泡泡id是否为附着物id
     * @param id 指定泡泡id
     */
    private _isAttachmentId(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _addAttachmentId id = ${id}`)

        return id && id[0] === AttachmentType
    }

    /**
     * 指定泡泡id是否含有附着物id
     * @param id 指定泡泡id
     */
    private _hasAttachmentId(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _addAttachmentId id = ${id}`)

        let idArr = JKManager.Utils.parseJSON(id) as number[] | number

        return idArr[1] && (idArr[1].toString())[0] === AttachmentType
    }

    /**
     * 为指定泡泡数据id添加或刷新附着物id
     * @param id 指定泡泡数据id
     * @param attachmentId 新的附着物id
     */
    private _addAttachmentId(id: string, attachmentId: string): string {
        // cc.log(`${cc.js.getClassName(this)} _addAttachmentId id = ${id} attachmentId = ${attachmentId}`)

        let idArr: number[] | number
        if (id.indexOf(`,`) >= 0) {
            idArr = JKManager.Utils.parseJSON(id) as number[]
            idArr[1] = Number(attachmentId)
        } else {
            idArr = [Number(id), Number(attachmentId)]
        }

        return JSON.stringify(idArr)
    }

    /**
     * 为指定泡泡数据id移除附着物id
     * @param id 指定泡泡数据id
     */
    private _removeAttachmentId(id: string): string {
        // cc.log(`${cc.js.getClassName(this)} _removeAttachmentId id = ${id}`)

        let idArr: number[] | number
        if (id.indexOf(`,`) >= 0) {
            idArr = JKManager.Utils.parseJSON(id) as number[]
            if (idArr[2]) {
                idArr[1] = 0
            } else {
                idArr = idArr[0]
            }
        } else {
            idArr = Number(id)
        }

        return JSON.stringify(idArr)
    }

    /**
     * 指定泡泡id是否为2级附着物id
     * @param id 指定泡泡id
     */
    private _isAttachment2Id(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _isAttachment2Id id = ${id}`)

        return id && id[0] === Attachment2Type
    }

    /**
     * 指定泡泡id是否含有2级附着物id
     * @param id 指定泡泡id
     */
    private _hasAttachment2Id(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _hasAttachment2Id id = ${id}`)

        let idArr = JKManager.Utils.parseJSON(id) as number[] | number

        return idArr[2] && (idArr[2].toString())[0] === Attachment2Type
    }

    /**
     * 为指定泡泡数据id添加或刷新2级附着物id
     * @param id 指定泡泡数据id
     * @param attachment2Id 新的2级附着物id
     */
    private _addAttachment2Id(id: string, attachment2Id: string): string {
        // cc.log(`${cc.js.getClassName(this)} _addAttachment2Id id = ${id} attachment2Id = ${attachment2Id}`)

        let idArr: number[] | number
        if (id.indexOf(`,`) >= 0) {
            idArr = JKManager.Utils.parseJSON(id) as number[]
            idArr[1] = idArr[1] || 0
            idArr[2] = Number(attachment2Id)
        } else {
            idArr = [Number(id), 0, Number(attachment2Id)]
        }

        return JSON.stringify(idArr)
    }

    /**
     * 为指定泡泡数据id移除2级附着物id
     * @param id 指定泡泡数据id
     */
    private _removeAttachment2Id(id: string): string {
        // cc.log(`${cc.js.getClassName(this)} _removeAttachment2Id id = ${id}`)

        let idArr: number[] | number
        if (id.indexOf(`,`) >= 0) {
            idArr = JKManager.Utils.parseJSON(id) as number[]
            if (idArr[2]) {
                if (idArr[1]) {
                    idArr = [idArr[0], idArr[1]]
                } else {
                    idArr = idArr[0]
                }
            }
        } else {
            idArr = Number(id)
        }

        return JSON.stringify(idArr)
    }

    /**
     * 指定泡泡id是否含有随机泡泡id
     * @param id 指定泡泡id
     */
    private _hasRandomBubbleId(id: string) {
        // cc.log(`${cc.js.getClassName(this)} _hasRandomBubbleId id = ${id}`)

        let idArr = JKManager.Utils.parseJSON(id) as number[] | number,
            bubbleId: string
        if (id.indexOf(`,`) >= 0) {
            bubbleId = idArr[0].toString()
        } else {
            bubbleId = id
        }

        return bubbleId[0] === RandomBubbleType
    }

    /**
     * 添加或刷新泡泡配置记录
     * @param bubbleDatas 泡泡数据
     */
    private _addBubblesRecord(bubbleDatas: BubbleData[]) {
        // cc.log(`${cc.js.getClassName(this)} _addBubblesRecord`)

        bubbleDatas.forEach(bubbleData => {
            let x = bubbleData.x,
                y = bubbleData.y
            if (x !== null && y !== null) {
                if (this._bubblesRecord.length > 0) {
                    for (let index = 0; index < this._bubblesRecord.length; index++) {
                        let record = this._bubblesRecord[index],
                            recordId = record.id,
                            recordX = record.x,
                            recordY = record.y
                        if (recordX === x && recordY === y) {
                            this._bubblesRecord[index] = bubbleData

                            break
                        }
                        if (y > recordY) {
                            let nextRecord = this._bubblesRecord[index + 1]
                            if (nextRecord) {
                            } else {
                                this._bubblesRecord.splice(index + 1, 0, bubbleData)

                                break
                            }
                        } else if (y === recordY) {
                            if (x < recordX) {
                                this._bubblesRecord.splice(index, 0, bubbleData)

                                break
                            } else {
                                let nextRecord = this._bubblesRecord[index + 1]
                                if (nextRecord) {
                                    let nextRecordId = nextRecord.id,
                                        nextRecordX = nextRecord.x,
                                        nextRecordY = nextRecord.y
                                    if (y < nextRecordY || (y === nextRecordY && x < nextRecordX)) {
                                        this._bubblesRecord.splice(index + 1, 0, bubbleData)

                                        break
                                    }
                                } else {
                                    this._bubblesRecord.splice(index + 1, 0, bubbleData)

                                    break
                                }
                            }
                        } else {
                            if (index === 0) {
                                this._bubblesRecord.unshift(bubbleData)
                            } else {
                                this._bubblesRecord.splice(index, 0, bubbleData)
                            }

                            break
                        }
                    }
                } else {
                    this._bubblesRecord.push(bubbleData)
                }
            }
        })

        // cc.log(this._bubblesRecord)
    }

    /**
     * 移除泡泡配置记录
     * @param bubbleDatas 泡泡数据
     */
    private _removeBubblesRecord(bubbleDatas: BubbleData[]) {
        // cc.log(`${cc.js.getClassName(this)} _removeBubblesRecord`)

        bubbleDatas.forEach(bubbleData => {
            for (let index = 0; index < this._bubblesRecord.length; index++) {
                const record = this._bubblesRecord[index]
                if (record.x === bubbleData.x && record.y === bubbleData.y) {
                    this._bubblesRecord.splice(index, 1)

                    break
                }
            }
        })

        
        // cc.log(this._bubblesRecord)
    }

    /**
     * 导出配置
     */
    private _exportGridDate() {
        cc.log(`${cc.js.getClassName(this)} _exportGridDate`)

        JKManager.UiManager.showLoadingMask()
        cc.log(JSON.stringify(this._gridData))
        let gridData: string[][] = []
        for (const y in this._gridData) {
            if (Object.prototype.hasOwnProperty.call(this._gridData, y)) {
                const _yData = this._gridData[y]
                let yData = []
                gridData.push(yData)
                for (const x in _yData) {
                    if (Object.prototype.hasOwnProperty.call(_yData, x)) {
                        const data = _yData[x]
                        let numData = Number(data)
                        yData.push(isNaN(numData) ? data : numData)
                    }
                }
            }
        }
        cc.log(gridData)
        let key = `${this.ebExportPath.string || `json`}`,
            data = {},
            strData: string,
            path: string
        data[key] = gridData
        strData = JSON.stringify(data, (key: string, value: any) => {
            cc.log(`${key}: ${value}`)
            if (value.length === 10 || value.length === 11) {
                return JSON.stringify(value)
            }
            return value
        }, `\t`)
        strData = strData.replace(/\"/ig, ``)
        strData = strData.replace(key, `"${key}"`)
        cc.log(strData)
        if (CC_JSB) {
            path = `${jsb.fileUtils.getWritablePath()}\\${this.ebExportPath.string}.json`
            jsb.fileUtils.writeStringToFile(strData, path)
        }
        JKManager.UiManager.showCommonDialogView(path, `ScaleIn`, () => {
            cc.log(`${cc.js.getClassName(this)} onConfirm`)
        })
        JKManager.UiManager.hideLoadingMask()
    }

    /**
     * 导入配置
     */
    private _importGridDate() {
        cc.log(`${cc.js.getClassName(this)} _importGridDate`)

        JKManager.UiManager.showLoadingMask()
        if (CC_PREVIEW) {
            let strData = `{
                "114": [
                    [702,[702,501],702,304,702,702,702,702,702,702,304],
                    [702,702,304,702,702,702,702,702,702,304],
                    [303,303,305,702,702,702,702,[702,501],702,304,702],
                    [[702,501],702,303,702,702,702,702,702,304,702],
                    [702,702,702,303,702,702,702,702,304,702,702],
                    [702,702,702,305,702,702,[702,501],305,303,303],
                    [702,702,702,303,702,702,702,303,702,702,702],
                    [702,702,303,702,702,702,303,702,702,702],
                    [303,303,305,[702,501],702,702,303,702,702,[702,501],702],
                    [702,304,702,702,702,305,702,702,702,702],
                    [702,304,702,702,702,702,303,702,702,[702,501],702],
                    [303,305,702,702,702,702,303,702,702,702],
                    [702,702,303,702,702,702,702,303,702,702,702],
                    [[702,501],702,305,702,[702,501],702,702,305,303,303],
                    [702,702,303,702,702,702,702,702,304,702,702],
                    [303,305,702,702,702,702,702,702,304,702],
                    [702,304,702,702,[702,501],702,702,702,305,303,303],
                    [702,304,702,702,702,702,702,303,702,702],
                    [702,702,304,702,702,[702,501],702,303,[702,501],[702,501],702],
                    [702,702,304,702,702,702,305,702,[702,501],702],
                    [303,303,303,305,702,702,702,303,702,702,702],
                    [702,702,702,303,702,702,702,303,702,702],
                    [702,[702,501],702,702,303,702,[702,501],702,305,303,303],
                    [702,702,[702,501],702,303,702,702,702,304,702],
                    [702,702,[702,501],702,702,303,702,[702,501],702,304,702],
                    [702,[702,501],702,702,702,303,702,702,702,304],
                    [702,702,702,702,702,702,305,702,702,702,304]
                ]
            }`
            this._bubblesRecord = []
            let gridData = JSON.parse(strData)
            this._gridData = gridData[Object.keys(gridData)[0]]
            let dataIsv: DataIsv = {
                row: Object.keys(this._gridData).length,
                longCol: Number(this.ebLongCol.string),
                shortCol: Number(this.ebshortCol.string),
            }
            this._dataUpdateTool.setCpntDataByName(JKManager.Const.MissionEditorView.CpntName.Isv, dataIsv)
        } else {
            let path = `${this.ebImportPath.string}`
            if (CC_JSB) {
                if (jsb.fileUtils.isFileExist(path)) {
                    try {
                        let strData = jsb.fileUtils.getStringFromFile(path)
                        this._bubblesRecord = []
                        let gridData = JSON.parse(strData)
                        this._gridData = gridData[Object.keys(gridData)[0]]
                        let dataIsv: DataIsv = {
                            row: Object.keys(this._gridData).length,
                            longCol: Number(this.ebLongCol.string),
                            shortCol: Number(this.ebshortCol.string),
                        }
                        this._dataUpdateTool.setCpntDataByName(JKManager.Const.MissionEditorView.CpntName.Isv, dataIsv)
                    } catch (error) {
                        JKManager.UiManager.showCommonDialogView(error, `ScaleIn`, () => {
                            cc.log(`${cc.js.getClassName(this)} onConfirm`)
                        })
                    }
                } else {
                    JKManager.UiManager.showCommonDialogView(`${path} is not exist!`, `ScaleIn`, () => {
                        cc.log(`${cc.js.getClassName(this)} onConfirm`)
                    })
                }
            }
        }
        JKManager.UiManager.hideLoadingMask()
    }

    /**
     * 将配置数据转换成id
     * @param gridData 
     */
    private _gridDataToIdString(gridData: GridData): string {
        // cc.log(`${cc.js.getClassName(this)} _gridDataToIdString gridData = ${gridData}`)

        let id: string
        if (gridData instanceof Array) {
            id = JSON.stringify(gridData)
        } else {
            id = gridData.toString()
        }

        return id
    }

    /**
     * 获取随即泡泡统计详情
     */
    private _getRandomCntDetails(): string {
        cc.log(`${cc.js.getClassName(this)} _getRandomCntDetails`)

        let msg = ``,
            randomCntDetails = cc.js.createMap()
        if (this._bubblesRecord.length > 0) {
            this._bubblesRecord.forEach(bubbleData => {
                let id = bubbleData.id
                if (this._hasRandomBubbleId(id)) {
                    let randomBubbleId: string
                    if (this._hasAttachmentId(id) || this._hasAttachment2Id(id)) {
                        let idArr = JKManager.Utils.parseJSON(id) as number[] | number,
                            bubbleId = idArr[0].toString(),
                            attachmentId = idArr[1].toString(),
                            attachment2Id = idArr[2] && idArr[2].toString()

                        randomBubbleId = bubbleId
                    } else {
                        randomBubbleId = id
                    }
                    if (randomBubbleId) {
                        randomCntDetails[randomBubbleId] = randomCntDetails[randomBubbleId] || 0
                        randomCntDetails[randomBubbleId]++
                    }
                }
            })
        }
        for (const randomId in randomCntDetails) {
            const randomCnt = randomCntDetails[randomId]

            msg += `${randomId}: ${randomCnt}\n`
        }

        return msg
    }

    // ====================================================================================
    // ===================================== 外部接口 ======================================
    // ====================================================================================
}